package com.hjy.backend.versionManager;

import com.hjy.backend.commons.AbstractCache;
import com.hjy.backend.dataManager.DataManager;
import com.hjy.backend.transactionManager.TransactionManager;
import com.hjy.backend.transactionManager.TransactionManagerImpl;
import com.hjy.common.error.Error;
import com.hjy.backend.utils.Panic;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class VersionManagerImpl extends AbstractCache<Entry> implements VersionManager {

    TransactionManager tm;
    DataManager dm;
    Map<Long, Transaction> activeTransaction;
    Lock lock;
    LockTable lt;


    public VersionManagerImpl(TransactionManager tm, DataManager dm) {
        super(0);
        this.tm = tm;
        this.dm = dm;
        this.activeTransaction = new HashMap<>();
        // 超级事务一直在线
        activeTransaction.put(TransactionManagerImpl.SUPER_XID, Transaction.newTransaction(TransactionManagerImpl.SUPER_XID, 0, null));
        this.lock = new ReentrantLock();
        this.lt = new LockTable();
    }

    /**
     * 撤销事务,abort 事务的方法则有两种，手动和自动。手动指的是调用 abort() 方法, 这种不会设置改事务为不活跃事务，
     * 而自动，则是在事务被检测出出现死锁时，会自动撤销回滚事务；或者出现版本跳跃时，也会自动回滚：
     *
     * @param xid         事务号
     * @param autoAborted 是否自动撤销
     */
    private void internAbort(long xid, boolean autoAborted) {
        lock.lock();
        Transaction t = activeTransaction.get(xid);
        if (!autoAborted) {
            activeTransaction.remove(xid);
        }
        lock.unlock();

        if (t.autoAborted) return;
        lt.remove(xid);
        tm.abort(xid);
    }

    /**
     * 释放对应的 entry, 跟着释放对应的 DataItem
     *
     * @param entry
     */
    public void releaseEntry(Entry entry) {
        super.release(entry.getUid());
    }

    /**
     * 读取一个 entry 的数据
     *
     * @param xid
     * @param uid
     * @return
     * @throws Exception
     */
    @Override
    public byte[] read(long xid, long uid) throws Exception {
        lock.lock();
        Transaction t = activeTransaction.get(xid);
        lock.unlock();

        if (t.err != null) {
            throw t.err;
        }

        Entry entry = null;
        try {
            entry = super.get(uid);
        } catch (Exception e) {
            if (e == Error.NullEntryException) {
                return null;
            } else {
                throw e;
            }
        }
        try {
            // 对于当前事务来说,这个记录是否是可见的
            if (Visibility.isVisible(tm, t, entry)) {
                return entry.data();
            } else {
                return null;
            }
        } finally {
            entry.release();
        }
    }

    /**
     * 一个事务的一个操作产生的数据插入到数据文件中
     *
     * @param xid
     * @param data
     * @return
     * @throws Exception
     */
    @Override
    public long insert(long xid, byte[] data) throws Exception {
        lock.lock();
        Transaction t = activeTransaction.get(xid);
        lock.unlock();

        if (t.err != null) {
            throw t.err;
        }

        // 包装成一个 Entry
        byte[] raw = Entry.wrapEntryRaw(xid, data);
        // 调用 DataManager 插入数据(底层操作了)
        return dm.insert(xid, raw);
    }

    /**
     * uid对应的entry删除为被事务xid删除
     * 一是可见性判断，二是获取资源的锁，三是版本跳跃判断。删除的操作只有一个设置 XMAX。
     * 注意, 这里的删除是逻辑删除, 实际上在 xx.db 中的数据是没有删除的
     * @param xid
     * @param uid
     * @return
     * @throws Exception
     */
    @Override
    public boolean delete(long xid, long uid) throws Exception {
        lock.lock();
        Transaction t = activeTransaction.get(xid);
        lock.unlock();

        if (t.err != null) {
            throw t.err;
        }
        Entry entry = null;
        try {
            entry = super.get(uid);
        } catch (Exception e) {
            if (e == Error.NullEntryException) {
                return false;
            } else {
                throw e;
            }
        }
        try {
            // 事务对该记录是否可见, 不可见不可操作
            if (!Visibility.isVisible(tm, t, entry)) {
                return false;
            }
            Lock l = null;

            // 释放资源, 测试死锁, 一般返回 null
            try {
                l = lt.add(xid, uid);
            } catch (Exception e) {
                // 死锁了
                t.err = Error.ConcurrentUpdateException;
                internAbort(xid, true);
                t.autoAborted = true;
                throw t.err;
            }
            if (l != null) {
                l.lock();
                l.unlock();
            }

            // 自己不能删除自己
            if (entry.getXmax() == xid) {
                return false;
            }

            // 修改操作不可以版本跳跃
            if (Visibility.isVersionSkip(tm, t, entry)) {
                t.err = Error.ConcurrentUpdateException;
                internAbort(xid, true);
                t.autoAborted = true;
                throw t.err;
            }

            entry.setXmax(xid);
            return true;

        } finally {
            entry.release();
        }
    }

    /**
     * begin() 开启一个事务，并初始化事务的结构，将其存放在 activeTransaction 中，用于检查和快照使用：
     *
     * @param level
     * @return
     */
    @Override
    public long begin(int level) {
        lock.lock();
        try {
            long xid = tm.begin();
            Transaction t = Transaction.newTransaction(xid, level, activeTransaction);
            activeTransaction.put(xid, t);
            return xid;
        } finally {
            lock.unlock();
        }
    }

    /**
     * commit() 方法提交一个事务，主要就是 free 掉相关的结构，并且释放持有的锁，并修改 TM 状态：
     *
     * @param xid
     * @throws Exception
     */
    @Override
    public void commit(long xid) throws Exception {
        lock.lock();
        // 将事务从当前活动事务列表中拿出来
        Transaction t = activeTransaction.get(xid);
        lock.unlock();

        try {
            if (t.err != null) {
                throw t.err;
            }
        } catch (NullPointerException n) {
            System.out.println(xid);
            System.out.println(activeTransaction.keySet());
            Panic.panic(n);
        }

        lock.lock();
        activeTransaction.remove(xid);
        lock.unlock();
        // 资源依赖图中清除掉这个事务
        lt.remove(xid);
        tm.commit(xid);
    }

    /**
     * abort 事务的方法则有两种，手动和自动。手动指的是调用 abort() 方法, 这种不会设置改事务为不活跃事务,
     * 而自动，则是在事务被检测出出现死锁时，会自动撤销回滚事务；或者出现版本跳跃时，也会自动回滚：
     *
     * @param xid
     */
    @Override
    public void abort(long xid) {
        internAbort(xid, false);
    }

    @Override
    protected Entry getForCache(long uid) throws Exception {
        Entry entry = Entry.loadEntry(this, uid);
        if (entry == null) {
            throw Error.NullEntryException;
        }
        return entry;
    }

    @Override
    protected void releaseForCache(Entry entry) {
        entry.remove();
    }
}
